home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Tech Arsenal 1
/
Tech Arsenal (Arsenal Computer).ISO
/
tek-20
/
pmpsrc11.zip
/
AX25LAPB.C
< prev
next >
Wrap
Text File
|
1991-07-30
|
18KB
|
806 lines
/*
AX25LABP.C -- Implements AX.25 link level routines
Poor Man's Packet (PMP)
Copyright (c) 1991 by Andrew C. Payne All Rights Reserved.
Permission to use, copy, modify, and distribute this software and its
documentation without fee for NON-COMMERCIAL AMATEUR RADIO USE ONLY is hereby
granted, provided that the above copyright notice appear in all copies.
The author makes no representations about the suitability of this software
for any purpose. It is provided "as is" without express or implied warranty.
August, 1989
Andrew C. Payne
*/
/* ----- Includes ----- */
#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
#include <bios.h>
#include <mem.h>
#include <alloc.h>
#include <string.h>
#include "pmp.h"
static void EmptyDQ(void);
/* ----- Local Variables ----- */
static struct ax25_packet *AX25_TXQueue[8]; /* TX Queue */
#define INC(x) (((x)+1) & 7) /* mod 7 increment */
#define DEC(x) (((x)-1) & 7) /* mod 7 decrement */
#define SECONDS(x) (BiosTime() + (x) * BIOSSEC)
/* ----- Timer Stuff ----- */
/* StartT1()
Starts timer T1 going. The T1 timeout is calculated from the
FRACK parameter, the number of digis in the connect path, AND the
number of bytes currently sitting in the TX queue (so that the
timer starts up *after* the TX contents are sent).
*/
void StartT1(void)
{
AX25_Control.t1 = SECONDS(Frack*(2 * AX25_Control.header.ndigis + 1)) +
TXQBytesInQ() * 8 / 1200 * BIOSSEC;
}
/* StopT1()
Stops timer T1.
*/
#define StopT1() (AX25_Control.t1 = 0)
/* StartT3()
Starts timer T3 (keep-alive).
*/
static void StartT3(void)
{
AX25_Control.t3 = SECONDS(Check);
}
/* StopT3()
Stops timer T3.
*/
#define StopT3() (AX25_Control.t3 = 0)
/* Connected()
Returns TRUE if the link is in the connected (information xfer)
state.
*/
int Connected(void)
{
switch(AX25_Control.state) {
case CONNECTED:
case RECOVERY:
return TRUE;
default:
return FALSE;
}
}
/* DumpState()
Shows the state of the TXQ on the status line (sort of like an
expanded 'STA' light).
This routine was originally for debugging, but it is kind of neat,
so I'm leaving it in.
*/
static void DumpState(void)
{
int i;
char s[10];
static char blank[] = " ";
/* go thorough all 8 TXQ entries */
if(Connected()) {
for(i=0; i<8; i++)
if(AX25_TXQueue[i] != NULL)
s[i] = '*';
else
s[i] = '-';
} else
strcpy(s,blank);
/* show the string on the status line */
putstring(64,25,8,0x70,s);
}
/* ----- Subroutines ----- */
/* AX25_Init()
Initializes the AX25 stuff in this module.
*/
void AX25_Init(void)
{
int i;
for(i=0; i<8; i++)
AX25_TXQueue[i] = NULL;
}
/* RecACK(n)
Given a sequence number, acknowledges all packets up to, but not
including that number.
If all frames are ACKed, T1 is axed, else T1 is restarted.
*/
static void RecACK(int n)
{
int i;
int ackcount;
/* go backwards through the AX25_TXQueue freeing ACKed packets */
ackcount = 0;
i = DEC(n);
while(AX25_TXQueue[i] != NULL) { /* was ACKed */
free(AX25_TXQueue[i]);
AX25_TXQueue[i] = NULL;
i = DEC(i);
ackcount++;
AX25_Control.unack--;
AX25_Control.qsize--;
}
if(ackcount)
AX25_Control.retries = 0; /* reset retries */
/* adjust T1 */
if(AX25_Control.unack == 0) {
StopT1(); /* no outstanding */
StartT3();
} else if(ackcount)
StartT1(); /* restart T1 */
DumpState();
}
/* FlushTXQ()
Discards any entries in the TX Queue. Done after a disconnect and
before a connect.
*/
static void FlushTXQ(void)
{
int i;
/* free up any non-NULL entries */
for(i=0; i<8; i++) {
if(AX25_TXQueue[i] != NULL) {
free(AX25_TXQueue[i]);
AX25_TXQueue[i] = NULL;
}
}
AX25_Control.qsize = AX25_Control.unack = 0;
DumpState();
}
/* SendControl(cmd,cmdresp)
Sends a command/response packet to the current destination specified
in the AX25_Control record.
If there is a pending ACK, it is cleared.
*/
static void SendControl(int cmd, int cmdresp)
{
/* add rec sequence to S packets */
if((cmd & 3) == 1) {
AX25_Control.response = 0;
cmd |= AX25_Control.vr << 5;
}
/* set up the header */
AX25_Control.header.dlen = 0;
AX25_Control.header.cont = cmd;
AX25_Control.header.cmdresp = cmdresp;
/* enqueue packet */
SendAX25(&AX25_Control.header);
}
/* ReplyDM(p)
Given an incoming packet (with path), sends a Disconnected Mode
reply.
*/
static void ReplyDM(struct ax25_packet *p)
{
struct ax25_packet p1;
ReversePath(&p1,p);
p1.dlen = 0;
p1.cont = DM|PF;
p1.cmdresp = RESPONSE;
SendAX25(&p1);
}
/* ChangeState(s)
Changes to the state given. Performs any processing needed for the
state change.
NOTE: if the change is to the CONNECTED state from any state but
RECOVERY, the link is reset.
*/
void ChangeState(int s)
{
/* already in this state?, if so, no change */
if(AX25_Control.state == s)
return;
/* notify the user what's happening */
NotifyStatus(AX25_Control.state, s);
/* for connects and disconnects, initialize */
if(s == DISCONNECTED || (s == CONNECTED && AX25_Control.state != RECOVERY)) {
StopT1();
StopT3();
AX25_Control.vs = 0;
AX25_Control.vr = 0;
AX25_Control.tqs = 0;
AX25_Control.response = 0;
AX25_Control.retries = 0;
AX25_Control.remotebusy = FALSE;
DQInit(&AX25_Control.dq); /* init data queue */
FlushTXQ(); /* flush queue */
}
/* set current state */
AX25_Control.state = s;
DumpState();
}
/* AX25_Open()
Starts setting up an AX25 connection.
NOTE: Assumes that the AX25_Control record has already been set up.
*/
void AX25_Open(void)
{
switch(AX25_Control.state) {
case DISCONNECTED:
case SETUP:
SendControl(SABM,COMMAND); /* send connect packet */
StartT1();
ChangeState(SETUP);
break;
}
}
/* ----- Incoming Packet Handling ----- */
/* AX25_Output()
Writes out any I frames sitting in the AX25_TXQueue, but that have
not been transmitted.
If an I-frame is sent, an implicit ACK is also sent, so the
'response' variable is cleared.
*/
static void AX25_Output(void)
{
int i;
/* should be in the CONNECTED or RECOVERY state and remote not busy */
if(AX25_Control.state != CONNECTED && AX25_Control.state != RECOVERY
&& !AX25_Control.remotebusy)
return;
/* send any frames ahead of us in the TX queue */
i = AX25_Control.vs; /* next frame to send */
while(AX25_TXQueue[i] != NULL) {
/* set up the control field and send the packet */
AX25_TXQueue[i]->cont = (i << 1) | (AX25_Control.vr << 5) | I;
SendAX25(AX25_TXQueue[i]);
/* the ack was piggybacked on the I frame... */
AX25_Control.response = 0;
i = INC(i);
AX25_Control.unack++;
/* if T1 is not running, start it up */
if(AX25_Control.t1 == 0) {
StartT1();
StopT3();
}
}
AX25_Control.vs = i;
}
/* AX25_Flush()
Called once at the end of each RXQueue pass. This routine sends
any enqueued I packets (if the remote is not busy) and any queued
response.
*/
void AX25_Flush(void)
{
/* write any pending output */
EmptyDQ(); /* copy from DQ */
AX25_Output();
/* if a pending ack left to send, send it */
if(AX25_Control.response)
SendControl(AX25_Control.response,RESPONSE);
}
/* upcall(p, len)
Called for each valid incoming packet.
*/
static void upcall(byte *p, int len)
{
#ifdef REMOTE
char s[80];
#endif
eol_in(EOL_CR, p, len); /* convert EOLs */
uputtext(NormalAttr, p, len);
#ifdef REMOTE
if(p[0] == '/' && p[1] == '/') {
memcpy(s, p, len);
s[len] = '\0'; /* terminate */
command(s+2);
}
#endif
}
/* AX25_Incoming(p)
Called with a pointer to a level 2 packet for each incoming packet
addressed to us.
*/
void AX25_Incoming(struct ax25_packet *p2)
{
int ftype; /* frame type */
int poll; /* command, poll? */
int final; /* reply, final? */
int nr; /* rec'd incoming frames */
int ns;
int other; /* TRUE if from other station */
other = !CompAX25Addr(&p2->source, &AX25_Control.header.dest);
ftype = FrameType(p2->cont); /* get frame type */
/* set up the poll or final flags */
poll = final = FALSE;
if(p2->cont & PF) {
if(p2->cmdresp == COMMAND)
poll = TRUE;
else
final = TRUE;
}
/* get the send/rec sequences for later use */
nr = (p2->cont >> 5) & 7;
ns = (p2->cont >> 1) & 7;
/* increment the RX counters */
switch(ftype) {
case REJ:
RXREJ++;
break;
case FRMR:
RXFRMR++;
break;
}
/* trap stray packets while connected */
if(AX25_Control.state != DISCONNECTED && !other) {
ReplyDM(p2);
return;
}
/* handle the packet, depending on the mode we are in */
switch(AX25_Control.state) {
/* disconnected mode */
case DISCONNECTED:
switch(ftype) {
case SABM: /* incoming connect */
ReversePath(&AX25_Control.header,p2);
AX25_Control.header.pid = PID_TEXT;
ChangeState(CONNECTED);
SendControl(UA|PF,RESPONSE);
SendWelcome();
StartT3();
break;
case DM:
break;
default: /* don't bother us! */
if(poll)
ReplyDM(p2);
break;
}
break;
/* link setup mode */
case SETUP:
switch(ftype) {
case SABM: /* simultaneous connect */
SendControl(UA|PF,RESPONSE);
ChangeState(CONNECTED);
SendWelcome();
StartT3();
break;
case DISC: /* disconnect */
SendControl(DM|PF,RESPONSE);
AX25_Control.dreason = DISC_BUSY;
ChangeState(DISCONNECTED);
break;
case UA: /* connection accept */
ChangeState(CONNECTED);
StartT3();
break;
case DM: /* connection refused */
AX25_Control.dreason = DISC_BUSY;
ChangeState(DISCONNECTED);
break;
default:
break; /* ignore */
}
break;
/* disconnect pending mode */
case DISCONNECTPEND:
switch(ftype) {
case SABM:
SendControl(DM|PF,RESPONSE);
break;
case DISC:
SendControl(UA|PF,RESPONSE);
break;
case UA:
case DM:
AX25_Control.dreason = DISC_LOCAL;
ChangeState(DISCONNECTED);
break;
default:
if(poll) {
SendControl(DM|PF,RESPONSE);
AX25_Control.dreason = DISC_LOCAL;
ChangeState(DISCONNECTED);
}
break;
}
break;
/* "we are connected" mode */
case CONNECTED:
switch(ftype) {
case SABM: /* link reset */
SendControl(UA|PF,RESPONSE);
ChangeState(CONNECTED);
StartT3();
break;
case DISC:
SendControl(UA|PF,RESPONSE);
AX25_Control.dreason = DISC_REMOTE;
ChangeState(DISCONNECTED);
break;
case DM:
AX25_Control.dreason = DISC_REMOTE;
ChangeState(DISCONNECTED);
break;
case UA:
AX25_Control.remotebusy = FALSE;
/* ignore */
break;
case FRMR: /* frame reject */
uprintf(InvAttr,"** Fatal -- Frame reject received. Resetting link...\n");
ChangeState(SETUP);
AX25_Open(); /* reset link */
break;
case REJ:
RecACK(nr); /* ACK our frames */
if(poll)
SendControl(RR|PF,RESPONSE);
StopT1(); /* re-xmit */
AX25_Control.vs -= AX25_Control.unack;
AX25_Control.vs &= 7;
AX25_Control.unack = 0;
AX25_Control.remotebusy = FALSE;
StartT3();
break;
case RNR:
case RR:
AX25_Control.remotebusy = (ftype == RNR);
RecACK(nr); /* ACK our frames */
if(poll) /* respond to polls */
SendControl(RR|PF,RESPONSE);
break;
case I:
RecACK(nr); /* ACK our frames */
if(ns == AX25_Control.vr) { /* in sequence? */
upcall(p2->data, p2->dlen);
AX25_Control.vr = INC(AX25_Control.vr);
if(poll)
SendControl(RR|PF, RESPONSE);
else
AX25_Control.response = RR;
} else { /* out of seq */
if(poll)
SendControl(REJ|PF, RESPONSE);
else
AX25_Control.response = REJ;
}
}
break;
/* "can we get out of this mess?" mode */
case RECOVERY:
switch(ftype) {
case SABM: /* link reset */
SendControl(UA|PF,RESPONSE);
ChangeState(CONNECTED);
StartT3();
break;
case DISC:
SendControl(UA,RESPONSE);
AX25_Control.dreason = DISC_REMOTE;
ChangeState(DISCONNECTED);
break;
case DM:
AX25_Control.dreason = DISC_REMOTE;
ChangeState(DISCONNECTED);
break;
case UA:
/* ignore */
AX25_Control.remotebusy = FALSE;
break;
case FRMR:
uprintf(InvAttr,"** Fatal -- Frame reject received. Resetting link...\n");
ChangeState(SETUP);
AX25_Open(); /* reset link */
break;
case RNR:
case RR:
AX25_Control.remotebusy = (ftype == RNR);
RecACK(nr);
if(final) { /* got a reply */
StopT1();
if(AX25_Control.unack) {
AX25_Control.vs -= AX25_Control.unack;
AX25_Control.vs &= 7;
AX25_Control.unack = 0;
} else {
ChangeState(CONNECTED);
StartT3();
}
} else {
if(poll) /* respond to poll */
SendControl(RR|PF,RESPONSE);
if(AX25_Control.t1 == 0)
StartT1();
}
break;
case REJ:
RecACK(nr);
if(final) {
StopT1();
if(AX25_Control.unack) {
AX25_Control.vs -= AX25_Control.unack;
AX25_Control.vs &= 7;
AX25_Control.unack = 0;
} else {
ChangeState(CONNECTED);
StartT3();
}
} else {
if(poll) /* enqueue reply */
SendControl(RR|PF,RESPONSE);
if(AX25_Control.unack) {
AX25_Control.vs -= AX25_Control.unack;
AX25_Control.vs &= 7;
AX25_Control.unack = 0;
}
if(AX25_Control.t1 == 0)
StartT1();
}
AX25_Control.remotebusy = FALSE;
break;
case I:
RecACK(nr);
if(AX25_Control.t1 == 0) /* keep T1 */
StartT1();
if(ns == AX25_Control.vr) { /* in sequence? */
upcall(p2->data, p2->dlen);
AX25_Control.vr = INC(AX25_Control.vr);
if(poll)
SendControl(RR|PF, RESPONSE);
else
AX25_Control.response = RR;
} else { /* out of seq */
if(poll)
SendControl(REJ|PF, RESPONSE);
else
AX25_Control.response = REJ;
}
break;
}
break;
}
}
/* AX25_Close()
Start closing an AX25 connection.
*/
void AX25_Close(void)
{
switch(AX25_Control.state) {
case DISCONNECTED: /* ignore */
break;
case DISCONNECTPEND:
AX25_Control.dreason = DISC_LOCAL;
ChangeState(DISCONNECTED);
break;
case CONNECTED: /* start disconnect */
case RECOVERY:
case SETUP:
AX25_Control.retries = 0;
SendControl(DISC,COMMAND);
StartT1();
ChangeState(DISCONNECTPEND);
break;
}
}
/* PollRemote()
Called when T1 times out in CONNECTED or RECOVERY mode to poll
the remote DXE.
*/
static void PollRemote(void)
{
int i;
/* if data sitting in the queue, use it */
if(AX25_Control.unack) {
i = (AX25_Control.vs - AX25_Control.unack) & 0x7;
if(AX25_TXQueue[i]->dlen <= Pthresh) {
/* set up the control field and send the packet */
AX25_TXQueue[i]->cont = (i << 1) | (AX25_Control.vr << 5) | I | PF;
SendAX25(AX25_TXQueue[i]);
StartT1();
return;
}
}
/* else, just send RR w/ poll */
SendControl(RR|PF,COMMAND);
StartT1();
}
/* T1Expire()
Gets called when T1 or T3 times out.
*/
static void T1Expire(void)
{
switch(AX25_Control.state) {
case SETUP: /* link setup */
if(Retry != 0 && AX25_Control.retries >= Retry) {
AX25_Control.dreason = DISC_TIMEOUT;
ChangeState(DISCONNECTED);
} else {
AX25_Control.retries++;
SendControl(SABM|PF,COMMAND);
StartT1();
}
break;
case DISCONNECTPEND: /* disconnect pending */
if(Retry != 0 && AX25_Control.retries >= Retry) {
AX25_Control.dreason = DISC_TIMEOUT;
ChangeState(DISCONNECTED);
} else {
AX25_Control.retries++;
SendControl(DISC|PF,COMMAND);
StartT1();
}
break;
case CONNECTED:
AX25_Control.retries = 0;
case RECOVERY: /* poll them */
if(Retry != 0 && AX25_Control.retries >= Retry) {
SendControl(DM|PF,RESPONSE);
AX25_Control.dreason = DISC_TIMEOUT;
ChangeState(DISCONNECTED);
} else {
AX25_Control.retries++;
PollRemote();
ChangeState(RECOVERY);
}
break;
}
}
/* AX25_Expire(n)
This routine gets called when one of the timers expires.
'n' is the timer number.
*/
void AX25_Expire(int n)
{
switch(n) {
case 1:
StopT1();
T1Expire();
break;
case 2:
AX25_Control.t2 = 0;
break;
case 3:
StopT3();
T1Expire();
break;
}
}
/* ----- Link Transmission ----- */
/* AX25QFull()
Returns TRUE if the AX.25 send queue is full (e.g. MaxFrames
outstanding).
*/
int AX25QFull(void)
{
return AX25_Control.qsize >= MaxFrame;
}
/* LinkSend(p,len)
Sends data across a connected AX.25 link. If text link, performs
appropriate EOL conversions.
Returns TRUE if error (not connected).
*/
int LinkSend(byte *p, int len)
{
struct dqentry *dq;
/* gotta be connected to do this stuff */
if(!Connected())
return TRUE;
DQAdd(&AX25_Control.dq, p, len);
if(AX25_Control.type == TEXT) {
dq = DQFirst(&AX25_Control.dq);
eol_out(EOL_CR, dq->data, dq->len);
}
return FALSE; /* no errors */
}
/* EmptyDQ()
Empties data, if any from the DQ into the AX.25 TX queue.
Ultimately, will merge packets in an attempt to get maximum sized
packets.
*/
static void EmptyDQ(void)
{
struct ax25_packet *ap;
int len;
/* loop until AX.25 queue full or DQueue empty */
while(!AX25QFull() && AX25_Control.dq.len) {
/* build an AX.25 packet, as long as possible */
len = min(AX25_Control.dq.len, Paclen);
ap = malloc(sizeof(struct ax25_packet) + len);
memcpy(ap,&AX25_Control.header,sizeof(struct ax25_packet));
ap->dlen = len;
ap->pid = PID_TEXT;
ap->cmdresp = COMMAND;
ap->cont = (AX25_Control.vr<<5) | (AX25_Control.tqs<<1) | I;
/* fill AX.25 packet from DQueue */
DQExtract(&AX25_Control.dq, ap->data, len);
/* add the packet to the AX.25 queue */
AX25_TXQueue[AX25_Control.tqs] = ap;
AX25_Control.tqs = INC(AX25_Control.tqs);
AX25_Control.qsize++;
DumpState();
}
}